#region Copyright notice and license
// Copyright 2015 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#endregion

using Google.Protobuf.Reflection;
using Grpc.Core;
using Grpc.Reflection.V1Alpha;

namespace Grpc.Reflection;

/// <summary>
/// Implementation of server reflection service.
/// </summary>
public class ReflectionServiceImpl : Grpc.Reflection.V1Alpha.ServerReflection.ServerReflectionBase
{
    readonly List<string> services;
    readonly SymbolRegistry symbolRegistry;

    /// <summary>
    /// Creates a new instance of <c>ReflectionServiceIml</c>.
    /// </summary>
    public ReflectionServiceImpl(IEnumerable<string> services, SymbolRegistry symbolRegistry)
    {
        this.services = new List<string>(services);
        this.symbolRegistry = symbolRegistry;
    }

    /// <summary>
    /// Creates a new instance of <c>ReflectionServiceIml</c>.
    /// </summary>
    public ReflectionServiceImpl(IEnumerable<ServiceDescriptor> serviceDescriptors)
    {
        this.services = new List<string>(serviceDescriptors.Select((serviceDescriptor) => serviceDescriptor.FullName));
        this.symbolRegistry = SymbolRegistry.FromFiles(serviceDescriptors.Select((serviceDescriptor) => serviceDescriptor.File));
    }

    /// <summary>
    /// Creates a new instance of <c>ReflectionServiceIml</c>.
    /// </summary>
    public ReflectionServiceImpl(params ServiceDescriptor[] serviceDescriptors) : this((IEnumerable<ServiceDescriptor>) serviceDescriptors)
    {
    }

    /// <summary>
    /// Processes a stream of server reflection requests.
    /// </summary>
    public override async Task ServerReflectionInfo(IAsyncStreamReader<ServerReflectionRequest> requestStream, IServerStreamWriter<ServerReflectionResponse> responseStream, ServerCallContext context)
    {
        while (await requestStream.MoveNext().ConfigureAwait(false))
        {
            var response = ProcessRequest(requestStream.Current);
            await responseStream.WriteAsync(response).ConfigureAwait(false);
        }
    }

    ServerReflectionResponse ProcessRequest(ServerReflectionRequest request)
    {
        switch (request.MessageRequestCase)
        {
            case ServerReflectionRequest.MessageRequestOneofCase.FileByFilename:
                return FileByFilename(request.FileByFilename);
            case ServerReflectionRequest.MessageRequestOneofCase.FileContainingSymbol:
                return FileContainingSymbol(request.FileContainingSymbol);
            case ServerReflectionRequest.MessageRequestOneofCase.ListServices:
                return ListServices();
            case ServerReflectionRequest.MessageRequestOneofCase.AllExtensionNumbersOfType:
            case ServerReflectionRequest.MessageRequestOneofCase.FileContainingExtension:
            default:
                return CreateErrorResponse(StatusCode.Unimplemented, "Request type not supported by C# reflection service.");
        }
    }

    ServerReflectionResponse FileByFilename(string filename)
    {
        FileDescriptor? file = symbolRegistry.FileByName(filename);
        if (file is null)
        {
            return CreateErrorResponse(StatusCode.NotFound, "File not found.");
        }

        var transitiveDependencies = new HashSet<FileDescriptor>();
        CollectTransitiveDependencies(file, transitiveDependencies);

        return new ServerReflectionResponse
        {
            FileDescriptorResponse = new FileDescriptorResponse { FileDescriptorProto = { transitiveDependencies.Select((d) => d.SerializedData) } }
        };
    }

    ServerReflectionResponse FileContainingSymbol(string symbol)
    {
        FileDescriptor? file = symbolRegistry.FileContainingSymbol(symbol);
        if (file is null)
        {
            return CreateErrorResponse(StatusCode.NotFound, "Symbol not found.");
        }

        var transitiveDependencies = new HashSet<FileDescriptor>();
        CollectTransitiveDependencies(file, transitiveDependencies);

        return new ServerReflectionResponse
        {
            FileDescriptorResponse = new FileDescriptorResponse { FileDescriptorProto = { transitiveDependencies.Select((d) => d.SerializedData) } }
        };
    }

    ServerReflectionResponse ListServices()
    {
        var serviceResponses = new ListServiceResponse();
        foreach (var serviceName in services)
        {
            serviceResponses.Service.Add(new ServiceResponse { Name = serviceName });
        }

        return new ServerReflectionResponse
        {
            ListServicesResponse = serviceResponses
        };
    }

    ServerReflectionResponse CreateErrorResponse(StatusCode status, string message)
    {
        return new ServerReflectionResponse
        {
            ErrorResponse = new ErrorResponse { ErrorCode = (int) status, ErrorMessage = message }
        };
    }

    void CollectTransitiveDependencies(FileDescriptor descriptor, HashSet<FileDescriptor> pool)
    {
        pool.Add(descriptor);
        foreach (var dependency in descriptor.Dependencies)
        {
            if (pool.Add(dependency))
            {
                // descriptors cannot have circular dependencies
                CollectTransitiveDependencies(dependency, pool);
            }
        }
    }
}
